package parser;

import parser.symbol.MyArray;
import parser.symbol.MyNumber;
import parser.symbol.Symbol;
import parser.symbol.Table;
import utils.CompileError;

import java.util.ArrayList;

import static parser.symbol.Table.llvm;
import static parser.symbol.Table.regCnt;

public class Instruction {
    public Instruction() {

    }

    private String prePostfix(Object o) {
        return "\t" + o.toString() + "\n";
    }

    public void alloca(Symbol symbol) throws CompileError {//形参分配内存
        String address = "%v" + regCnt++;
        if (symbol instanceof MyArray) {//%3 = alloca i32*
            llvm.append(prePostfix(address + " = alloca i32* "));
            llvm.append(prePostfix("store i32* " + symbol.getAddress() + ",i32* *" + address));//store i32*  %1, i32* * %3
            symbol.setAddress("* " + address);
        } else {
            llvm.append(prePostfix(address + " = alloca i32 "));
            llvm.append(prePostfix("store i32 " + symbol.getAddress() + ",i32* " + address));
            symbol.setAddress(address);
        }
    }

    /**
     * 分配内存时把变量  放入 符号表
     *
     * @param identName
     * @return
     */
    public Symbol alloca(String identName) throws CompileError {//int a的时候会分配内存，目标语言只会定义i32类型
        String reg = "%v" + (Table.regCnt++);
        Symbol symbol = new Symbol(identName, reg, "i32");
        Table.getInstance().addSymbol(identName, symbol);
        Table.llvm.append(prePostfix(reg + " = alloca i32"));
        return symbol;
    }

    /**
     * 数组内存分配
     *
     * @param tokenName
     * @param length
     * @return
     * @throws CompileError
     */
    public Symbol alloca(String tokenName, int length) throws CompileError {
        String address = "%a" + (Table.regCnt++);
        MyArray array = new MyArray(address);
        Table.getInstance().addSymbol(tokenName, array);

        Table.llvm.append(prePostfix(address + " = alloca [" + length + " x i32]"));
        return array;
    }

    /**
     * store i32 %2, i32* %1
     *
     * @param result 将表达式计算结果存到目标中
     * @param target
     */
    public void store(Symbol result, Symbol target) throws CompileError {
        if (target.isConst() && !result.isConst()) {
            throw new CompileError("常量不能被变量赋值");
        }
        Table.llvm.append(prePostfix("store i32 " + result.getPrintAddr() + ", i32* " + target.getAddress()));
    }

    /**
     * br i1 %7,label %8, label %10
     *
     * @param cond
     * @param trueLabel  两个label是不带%的
     * @param falseLabel
     */
    public void br(Symbol cond, String trueLabel, String falseLabel) {
        Table.llvm.append(prePostfix("br i1 " + cond.getAddress() + ",label %" + trueLabel + ", label %" + falseLabel));
    }

    /**
     * br label %9
     *
     * @param label 无条件跳转
     */
    public void br(String label) {
        Table.llvm.append(prePostfix("br label %" + label));
    }

    public void labelBegin(String label) {
        Table.llvm.append(label + ":\n");
    }

    /**
     * ret i32 0
     *
     * @param symbol 函数返回值类型只有int和void
     */
    public void ret(Symbol symbol) {
        Table.llvm.append(prePostfix("ret i32 " + symbol.getPrintAddr()));
    }

    public void ret() {
        llvm.append(prePostfix("ret void "));
    }

    //%5 = load i32, i32* %3
    public Symbol load(Symbol from) {
        String address = "%v" + regCnt++;
        Symbol result = new Symbol(address);
        if (from instanceof MyArray) {
            result = new MyArray(address, (MyArray) from);
        }
        if (from.isConst())
            result.setConst(true);
        Table.llvm.append(prePostfix(result.getAddress() + " = load i32, i32* " + from.getAddress()));
        return result;
    }

    /**
     * icmp	<result> = icmp <cond> <ty> <op1>, <op2>
     *
     * @param op1
     * @param op2
     * @param type
     * @return
     * @throws CompileError
     */
    public Symbol icmp(Symbol op1, Symbol op2, String type) throws CompileError {
        String addr = "%v" + (Table.regCnt++);
        Symbol result = new Symbol(addr);
        String cond;
        switch (type) {
            case "==":
                cond = "eq";
                break;
            case "!=":
                cond = "ne";
                break;
            case "<":
                cond = "slt";
                break;
            case "<=":
                cond = "sle";
                break;
            case ">":
                cond = "sgt";
                break;
            case ">=":
                cond = "sge";
                break;
            default:
                throw new CompileError("未知的比较运算");
        }
        result.setType("i1");
        Table.llvm.append(prePostfix(result.getAddress() + " = icmp " + cond + " i32 "
                + op1.getPrintAddr() + ", " + op2.getPrintAddr()));
        return result;
    }


    /**
     * //貌似i32不能转为i1
     *
     * @param symbol
     * @return <result> = zext <ty> <value> to <ty2>
     * @throws CompileError
     */
    public Symbol zext(Symbol symbol) throws CompileError {
        String addr = symbol.getPrintAddr();
        Symbol result = new Symbol("%v" + (Table.regCnt++));
        if (symbol.isI32()) {
            result.setType("i1");
            Table.llvm.append(prePostfix(result.getAddress() + " = zext i32 " + addr + " to i1"));
        } else {
            result.setType("i32");
            Table.llvm.append(prePostfix(result.getAddress() + " = zext i1 " + addr + " to i32"));
        }
        return result;
    }

    /**
     * <result> = add <ty> <op1>, <op2>
     * <result> = and <ty> <op1>, <op2>
     * <result> = or <ty> <op1>, <op2>
     *
     * @param x
     * @param y
     * @param op
     * @return
     * @throws CompileError
     */
    public Symbol operate(Symbol x, Symbol y, String op) throws CompileError {
        String type;
        String resType = "i32";
        switch (op) {
            case "+":
                type = "add";
                break;
            case "-":
                type = "sub";
                break;
            case "*":
                type = "mul";
                break;
            case "/":
                type = "sdiv";
                break;
            case "%":
                type = "srem";
                break;
            case "&&":
                type = "and";
                resType = "i1";
                break;
            case "||":
                type = "or";
                resType = "i1";
                break;
            default:
                throw new CompileError("未知算数操作" + op);
        }
        String addr = "%v" + (Table.regCnt++);
        Symbol result = new Symbol(addr);
        if (x instanceof MyNumber && y instanceof MyNumber) {
            int res, xx = ((MyNumber) x).getValue(), yy = ((MyNumber) y).getValue();
            if (op.equals("+")) {
                res = xx + yy;
                result = new MyNumber(res);
            } else if (op.equals("-")) {
                res = xx - yy;
                result = new MyNumber(res);
            } else if (op.equals("*")) {
                res = xx * yy;
                result = new MyNumber(res);
            } else if (op.equals("/")) {
                res = xx / yy;
                result = new MyNumber(res);
            } else if (op.equals("%")) {
                res = xx % yy;
                result = new MyNumber(res);
            }
            result.setAddress(addr);
        }
        if (x.isConst() && y.isConst()) {
            result.setConst(true);
        }
        //<result> = add <ty> <op1>, <op2>
        if (resType.equals("i32"))
            result.setType("i32");
        else
            result.setType("i1");
        Table.llvm.append(prePostfix(addr + " = " + type + " " + resType + " " + x.getPrintAddr() + ", " + y.getPrintAddr()));
        return result;
    }

    /**
     * @param symbol 符号加入全局表 输出llvm
     * @throws CompileError
     */
    public void setGlobal(Symbol symbol, boolean allZero) throws CompileError {
        if (symbol instanceof MyArray) {

            MyArray array = (MyArray) symbol;
            Table.getInstance().addGlobal(array.getName(), array);
            //@a = dso_local global [3 x i32] [i32 1, i32 2, i32 0]
            StringBuilder strCont = new StringBuilder("[");
            int length = array.getLength();
            ArrayList<Symbol> content = array.getContent();
            for (int i = 0; i < length; i++) {
                if ((content.size() <= i)) {
                    content.add(new MyNumber(0));
                }
                if (content.get(i) instanceof MyNumber) {
                    strCont.append("i32 ").
                            append(((MyNumber) content.get(i)).getValue())
                            .append(",");
                } else {
                    throw new CompileError("这里应该是MyNumber！！");
                }
            }
            strCont.replace(strCont.length() - 1, strCont.length(), "]");
            if (allZero) {
                Table.llvm.append(array.getAddress() + " = dso_local global [" + length + "x i32]  zeroinitializer\n");
            } else {
                Table.llvm.append(array.getAddress() + " = dso_local global [" + length + "x i32] " + strCont + "\n");
            }
        } else {
            Table.getInstance().addGlobal(symbol.getName(), symbol);
            Table.llvm.append(symbol.getAddress() + " = dso_local global i32 " + symbol.getInitVal() + "\n");
        }
    }

    /**
     * getelementptr [2 x i32], [2 x i32]* %3, i32 0, i32 0
     *
     * @param array  数组
     * @param symbol 记录要第几个的那个符号
     * @return
     * @throws CompileError
     */
    public MyArray getElementPtr(MyArray array, Symbol symbol) throws CompileError {
        String addr = "%v" + (Table.regCnt++);
        MyArray result = new MyArray(addr, array);
        if (array.isConst())
            result.setConst(true);
        int length = array.getLength();
        if (length == 0) {
            Symbol tmp = new Symbol("%v" + regCnt++);
            llvm.append(prePostfix(tmp.getAddress() + " = load i32* ,i32 * " + array.getAddress()));
            Table.llvm.append(prePostfix(addr + " = getelementptr i32, i32 * " + tmp.getAddress() + ", i32 " + symbol.getPrintAddr()));
        } else {
            Table.llvm.append(prePostfix(addr + " = getelementptr [" + length + " x i32], [" + length + " x i32]* " + array.getAddress() + ", i32 0, i32 " + symbol.getPrintAddr()));
        }
        return result;
    }

}
